// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

class Logger {
    constructor(level = Logger.level.warn, tags = [], size = 10000, isSilent = false) {
        this.level = level;
        this.tags = tags;
        this.size = size;
        this.isSilent = isSilent;
        this.timeline = [];
    }

    echo(level, tags, ...message) {
        let logLevel = Logger.level[level];
        // 生产环境非最高级别错误日志直接退出
        /*global __IS_DEBUG__*/
        if (!__IS_DEBUG__ && logLevel < Logger.level.error) {
            return;
        }

        // 允许不传递 tags，此时 tag 用各个 level 对应的默认值
        if (typeof tags === 'string') {
            message.unshift(tags);
            tags = [Logger.tag[level]];
        }

        if (logLevel >= this.level && this.match(tags, this.tags)) {
            const now = performance && performance.now();
            this.cache(now, tags, ...message);
            if (!this.isSilent) {
                this.triggerLogCallback(logLevel, now, tags, message);
            }
        }
    }

    match(tags1 = [], tags2 = []) {
        for (let i = 0, len = tags1.length; i < len; i++) {
            if (tags2.includes(tags1[i])) {
                return true;
            }
        }

        return false;
    }

    reset() {
        Logger.cache = [];
        Logger.callbacks = [];
    }

    cache(timestamp, tags, ...message) {
        if (Logger.cache.length >= this.size) {
            Logger.cache.shift();
        }

        Logger.cache.push({
            timestamp,
            tags: tags,
            message: message.join('| ')
        });
    }

    ana() {
        let logs = Logger.cache;
        let tmp = {};
        logs.forEach(item => {
            let tags = item.tags || [];
            let traceId = tags.find && tags.find(tag => tag.startsWith('trace_'));
            let name = tags.filter && tags.filter(tag => !tag.startsWith('trace_')).join(', ');
            if (traceId) {
                !tmp[traceId] && (tmp[traceId] = {id: traceId});
                tmp[traceId][name] = item.timestamp;
            }
        });
        let allTotal = 0;
        let allDecode = 0;
        let allRender = 0;
        let allOther = 0;
        let result = Object.keys(tmp).map(trace => {
            let total = tmp[trace]['decode, end'] - tmp[trace]['package, begin'];
            let decode = tmp[trace]['decode, end'] - tmp[trace]['decode, begin'];
            let render = tmp[trace]['render, end'] - tmp[trace]['render, begin'];
            let other = total - decode;
            allTotal += total;
            allDecode += decode;
            allRender += render;
            allOther += other;
            tmp[trace]['other/render/decode/total'] = `${other}/${render}/${decode}/${total}`;
            return tmp[trace];
        });
        const len = result.length;
        result.unshift({'id': '平均', 'other/render/decode/total': `${allOther / len}/${allRender / len}/${allDecode / len}/${allTotal / len}`});
        result.unshift({'id': '总计', 'other/render/decode/total': `${allOther}/${allRender}/${allDecode}/${allTotal}`});
        this.debug([Logger.tag.debug], result);
    }

    analysis() {
        let logs = Logger.cache;
        let result = [];
        let start;
        let recv = [];
        let video = [];
        logs.forEach(item => {
            if (item.tags.includes('delay')) {
                if (start) {
                    result.push({
                        start: start,
                        end: item,
                        recv: recv,
                        video: video,
                        period: (item.timestamp - start.timestamp) + 'ms',
                        delayChange: (Number(item.message) - Number(start.message)) * 1000 + 'ms',
                        recvLen: recv.length + '帧',
                        videoLen: video.length + '帧'

                    });
                    recv = [];
                    video = [];
                }

                start = item;
            } else if (item.tags.includes('video')) {
                video.push(item);
            } else if (item.tags.includes('recv')) {
                recv.push(item);
            }
        });
        this.debug([Logger.tag.debug], result);
        return result;
    }

    videoAna() {
        let logs = Logger.cache;
        let tmp = {};
        logs.forEach(item => {
            let tags = item.tags || [];
            let traceId = tags.find && tags.find(tag => tag.startsWith('trace_'));
            let name = tags.filter && tags.filter(tag => !tag.startsWith('trace_')).join(', ');
            if (traceId) {
                !tmp[traceId] && (tmp[traceId] = {id: traceId});
                // 'event, appending, buffer' 用于查看video缓存信息
                // 'recv, diffT' 用于计算接收视频数据间隔
                if (name === 'event, appending, buffer' || name === 'recv, diffT') {
                    tmp[traceId][name] = item.message;
                } else {
                    tmp[traceId][name] = item.timestamp;
                }               
            }
        });
        let allTotal = 0;
        let allFormat = 0;
        let allAppending1 = 0;
        let allAppending2 = 0;
        let allToBuffer = 0;

        let maxTotal = 0;
        let maxFormat = 0;
        let maxAppending1 = 0;
        let maxAppending2 = 0;
        let maxToBuffer = 0;
        let result = Object.keys(tmp).map(trace => {
            let total = Number((tmp[trace]['event, appendBuffer, 1'] - tmp[trace]['analysis, end']).toFixed(3));
            // let analysis = (tmp[trace]['analysis, end'] - tmp[trace]['event, recv']).toFixed(3));
            let format = Number((tmp[trace]['format, end'] - tmp[trace]['analysis, end']).toFixed(3));
            let appending1 = Number((tmp[trace]['event, appendBuffer, 0'] - tmp[trace]['format, end']).toFixed(3));
            let appending2 = Number((tmp[trace]['event, appendBuffer, 1'] - tmp[trace]['event, appendBuffer, 0']).toFixed(3));
            let formatToBuffer = Number((tmp[trace]['event, appendBuffer, 1'] - tmp[trace]['format, end']).toFixed(3));

            maxTotal = Math.max(maxTotal, total);
            maxFormat = Math.max(maxFormat, format);
            maxAppending1 = Math.max(maxAppending1, appending1);
            maxAppending2 = Math.max(maxAppending2, appending2);
            maxToBuffer = Math.max(maxToBuffer, formatToBuffer);

            allTotal += total;
            allFormat += format;
            allAppending1 += appending1;
            allAppending2 += appending2;
            allToBuffer += formatToBuffer;
            tmp[trace]['format/appending1/appending2/formatToBuffer/total'] = `${format}/${appending1}/${appending2}/${formatToBuffer}/${total}`;
            return tmp[trace];
        });
        const resultLen = result.length;
        result.unshift({'id': '最大值', 'format/appending1/appending2/formatToBuffer/total': `${maxFormat}/${maxAppending1}/${maxAppending2}/${maxToBuffer}/${maxTotal}`});
        result.unshift({'id': '平均', 'format/appending1/appending2/formatToBuffer/total': 
            `${(allFormat / resultLen).toFixed(3)}/${(allAppending1 / resultLen).toFixed(3)}/${(allAppending2 / resultLen).toFixed(3)}/${(allToBuffer / resultLen).toFixed(3)}/${(allTotal / resultLen).toFixed(3)}`});
        result.unshift({'id': '总计', 'format/appending1/appending2/formatToBuffer/total': 
            `${allFormat.toFixed(3)}/${allAppending1.toFixed(3)}/${allAppending2.toFixed(3)}/${allToBuffer.toFixed(3)}/${allTotal.toFixed(3)}`});
        
        this.debug([Logger.tag.debug], result);
        this.debug([Logger.tag.debug], '接收视频帧间隔时间大于70', result.filter(item => item['recv, diffT'] && Number(item['recv, diffT']) > 70));
        // 计算帧率
        const videoResult = result.filter(item => Boolean(item['analysis, end']));
        const videoResultLen = videoResult.length;
        if (videoResultLen > 0) {
            const firstTime = videoResult[0] && Number(videoResult[0].id.slice(6));
            const lastTime = videoResult[videoResultLen - 1] && Number(videoResult[videoResultLen - 1].id.slice(6));
            this.debug([Logger.tag.debug], 'fps: ' + (videoResultLen - 1) * 1000 / (lastTime - firstTime));
        }
    }

    table(tags) {
        let list = Logger.cache;
        if (tags && tags.length) {
            list = Logger.cache.filter(item => this.match(tags, item.tags));
        }

        this.debug([Logger.tag.debug], list);
    }

    toCSV() {
        let list = Logger.cache.map(item => `${String(item.timestamp)},${item.tags},${item.message}`);
        list.unshift('timestamp,tags,message');
        return [list.join('\n')];
    }

    export() {
        let data = new Blob(this.toCSV(), {type: 'application/octet-stream'});
        let anchor = document.createElement('a');
        anchor.href = window.URL.createObjectURL(data);
        anchor.download = `log-${Date.now()}.csv`;
        document.body.appendChild(anchor);
        anchor.click();
        document.body.removeChild(anchor);
        window.URL.revokeObjectURL(data);
    }

    debug(tags, ...args) {
        this.echo('debug', tags, ...args);
    }

    info(tags, ...args) {
        this.echo('info', tags, ...args);
    }

    warn(tags, ...args) {
        this.echo('warn', tags, ...args);
    }

    error(tags, ...args) {
        this.echo('error', tags, ...args);
    }

    triggerLogCallback(echoLevel, time, tags, message) {
        Logger.callbacks.forEach(item => {
            // 触发级别等于或低于 echoLevel 的所有事件，高级别消息通知可以触发低级别事件，低级别消息通知不能触发高级别事件
            if (echoLevel >= item.level && this.match(item.tags, tags)) {
                item.func({time, tags, message});
            }
        });
    }

    onLogReceived(func) {
        if (typeof func === 'function') {
            Logger.callbacks.push({
                level: this.level,
                tags: this.tags,
                func
            });
        }
    }
}
Logger.cache = [];
Logger.callbacks = [];
Logger.level = {
    debug: 1,
    info: 2,
    warn: 3,
    error: 4
};
Logger.tag = {
    debug: 'DEBUG',
    info: 'INFO',
    warn: 'WARN',
    error: 'ERROR'
};

// 生产环境限制日志级别为最高级，用户只能获取错误日志
let logLevel = Logger.level.error;
let logTags = [Logger.tag.error];
/*global __IS_DEBUG__*/
if (__IS_DEBUG__) {
    logLevel = Logger.level.debug;
    logTags.push(Logger.tag.debug);
}

let logger = new Logger(logLevel, logTags);
export default logger;
